package cn.starlightsoftware.sherly.service.auth;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.json.JSONUtil;
import cn.hutool.jwt.JWTUtil;
import cn.starlightsoftware.sherly.config.TenantConfig;
import cn.starlightsoftware.sherly.controller.admin.auth.vo.LoginReqVO;
import cn.starlightsoftware.sherly.controller.admin.auth.vo.LoginRespVO;
import cn.starlightsoftware.sherly.exception.BizException;
import cn.starlightsoftware.sherly.db.mysql.permission.PermissionMapper;
import cn.starlightsoftware.sherly.db.mysql.permission.RoleMapper;
import cn.starlightsoftware.sherly.db.mysql.permission.RolePermissionMapper;
import cn.starlightsoftware.sherly.db.mysql.permission.UserRoleMapper;
import cn.starlightsoftware.sherly.db.mysql.user.UserMapper;
import cn.starlightsoftware.sherly.db.model.permission.PermissionDO;
import cn.starlightsoftware.sherly.db.model.permission.RoleDO;
import cn.starlightsoftware.sherly.db.model.permission.RolePermissionDO;
import cn.starlightsoftware.sherly.db.model.permission.UserRoleDO;
import cn.starlightsoftware.sherly.db.model.user.UserDO;
import cn.starlightsoftware.sherly.security.entity.LoginUserDetails;
import cn.starlightsoftware.sherly.security.utils.SecurityUtil;
import cn.starlightsoftware.sherly.utils.StreamUtil;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.TimeUnit;

import static cn.starlightsoftware.sherly.constants.RedisKeyConstants.SESSION_ID;
import static cn.starlightsoftware.sherly.enums.UsableEnum.DISABLE;
import static cn.starlightsoftware.sherly.exception.enums.SystemErrorCodeEnum.*;
import static cn.starlightsoftware.sherly.constants.SecurityConstants.INFO_PHONE;
import static cn.starlightsoftware.sherly.constants.SecurityConstants.INFO_USER_NAME;

/**
 * @author 谷子毅
 * @date 2024/5/7
 */
@Service
public class AuthServiceImpl implements AuthService {

    @Resource
    private TenantConfig tenantConfig;

    @Resource
    private UserMapper userMapper;

    @Resource
    private RoleMapper roleMapper;

    @Resource
    private UserRoleMapper userRoleMapper;

    @Resource
    private PermissionMapper permissionMapper;

    @Resource
    private RolePermissionMapper rolePermissionMapper;

    @Resource
    private PasswordEncoder passwordEncoder;

    @Resource
    private RedisTemplate<String, Object> redisTemplate;

    @Override
    public LoginRespVO login(LoginReqVO reqVO) {

        // 还未登陆，所以需要设置当前需要操作的租户数据库
        SecurityUtil.setOperateTenantCode(tenantConfig.getMainTenantCode());

        // 认证
        UserDO user = authenticate(reqVO.getUsername(), reqVO.getPassword());

        // 获取登录信息
        LoginUserDetails loginUser = createLoginUserDetails(user);

        String sessionId = IdUtil.simpleUUID();

        // 缓存登录信息
        redisTemplate.opsForValue().set(SESSION_ID + sessionId, JSONUtil.toJsonStr(loginUser), 6, TimeUnit.HOURS);

        // 生成token并返回
        Map<String, Object> map = new HashMap<>();
        map.put("sessionId", sessionId);
        return new LoginRespVO().setToken(JWTUtil.createToken(map, "sherly".getBytes(StandardCharsets.UTF_8)));
    }

    private LoginUserDetails createLoginUserDetails(UserDO user) {
        List<UserRoleDO> userRoleList = userRoleMapper.selectList(UserRoleDO::getUserId, user.getUserId());
        Set<String> roleCodes = new HashSet<>();
        Set<String> permissionCodes = new HashSet<>();

        if (CollectionUtil.isNotEmpty(userRoleList)) {
            Set<String> roleIds = StreamUtil.toSet(userRoleList, UserRoleDO::getRoleId);

            List<RoleDO> roles = roleMapper.selectBatchIds(roleIds);
            if (CollectionUtil.isNotEmpty(roles)) {
                roleCodes.addAll(StreamUtil.toSet(roles, RoleDO::getRoleCode));

                List<RolePermissionDO> rolePermissionList = rolePermissionMapper.selectList(RolePermissionDO::getRoleId, roleIds);
                if (CollectionUtil.isNotEmpty(rolePermissionList)) {
                    Set<String> permissionIds = StreamUtil.toSet(rolePermissionList, RolePermissionDO::getPermissionId);

                    List<PermissionDO> permissions = permissionMapper.selectBatchIds(permissionIds);
                    if (CollectionUtil.isNotEmpty(permissions)) {
                        permissionCodes.addAll(StreamUtil.toSet(permissions, PermissionDO::getPermissionCode));
                    }
                }
            }
        }

        Set<String> scopes = new HashSet<>();
        roleCodes.forEach(roleCode -> scopes.add("ROLE_" + roleCode));
        scopes.addAll(permissionCodes);

        Map<String, String> info = new HashMap<>();
        info.put(INFO_PHONE , user.getPhone());
        info.put(INFO_USER_NAME, user.getUsername());

        return new LoginUserDetails()
                .setLoginUserId(user.getUserId())
                .setTenantCode(SecurityUtil.getTenantCode())
                .setScopes(scopes)
                .setInfo(info);
    }


    @Override
    public UserDO authenticate(String username, String password) {
        UserDO user = userMapper.selectOne(UserDO::getUsername, username);

        // 检查用户名是否存在
        if (ObjUtil.isNull(user)) {
            throw new BizException(USERNAME_NOT_FOUND);
        }

        // 判断密码是否匹配
        if (!passwordEncoder.matches(password, user.getPassword())) {
            throw new BizException(USERNAME_PASSWORD_NOT_MATCH);
        }

        // 判断是否被禁用
        if (ObjUtil.equal(user.getUsable(), DISABLE)) {
            throw new BizException(USERNAME_DISABLE);
        }

        return user;
    }


}
